package fr.babos.graphic.components.controls.lists 
{
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.DisplayObject;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Matrix;
	import flash.utils.getTimer;
	import fr.babos.graphic.components.base.BaseContainer;
	import fr.babos.graphic.components.base.IResizableComponent;
	import fr.babos.graphic.components.base.ResizableComponent;
	import fr.babos.touch.delegate.IGestureTouchDelegate;
	import fr.babos.touch.emulator.GestureTouchEmulator;
	import fr.babos.touch.events.GestureTouchDragEvent;
	import fr.babos.touch.events.GestureTouchEvent;
	import fr.babos.utils.ObjectUtils;
	import org.osflash.signals.Signal;
	
	/**
	 * @author ZoulouX
	 */
	public class VirtualList extends BaseContainer implements IGestureTouchDelegate
	{
		/**
		 * Pas de masqué appliqué
		 */
		public static const NO_MASK_TYPE			:uint 							= 0;
		
		/**
		 * Double masque appliqué (un masque pour le premier élément, un pour le dernier)
		 */
		public static const DOUBLE_MASK_TYPE		:uint 							= 1;
		
		/**
		 * Masque par scaling (le premier et le dernier élément seront scalés)
		 */
		public static const SCALE_MASK_TYPE			:uint 							= 2;
		
		/**
		 * Masque par alpha (le premier et le dernier élément disparaitront en alpha)
		 */
		public static const ALPHA_MASK_TYPE			:uint 							= 3;
		
		/**
		 * L'orientation horizontale
		 */
		public static const HORIZONTAL_ORIENTATION	:String							= "horizontal";
		
		/**
		 * L'orientation verticale
		 */
		public static const VERTICAL_ORIENTATION	:String							= "vertical";
		
		/**
		 * Les propriétés de directions
		 */
		protected static const ORIENTATIONS_VAR_NAMES:Object	= {
			horizontal: {
				_positionVar			: "x",
				_contentSizeVar			: "width",
				_deltaTouchVar			: "xDelta",
				_scaleVar				: "scaleX",
				_dragDirection			: "isHorizontal",
				_placementVar1			: "top",
				_placementVar2			: "bottom",
				_containerPlacementVar1	: "left",
				_containerPlacementVar2	: "right"
			},
			vertical: {
				_positionVar			: "y",
				_contentSizeVar			: "height",
				_deltaTouchVar			: "yDelta",
				_scaleVar				: "scaleY",
				_dragDirection			: "isVertical",
				_placementVar1			: "left",
				_placementVar2			: "right",
				_containerPlacementVar1	: "top",
				_containerPlacementVar2	: "bottom"
			}
		}
		
		
		/**
		 * Le type de masque appliqué
		 */
		protected var _maskType						:uint;
		
		/**
		 * Le premier masque
		 */
		protected var _firstMask					:DisplayObject;
		
		/**
		 * Le dernier masque
		 */
		protected var _lastMask						:DisplayObject;
		
		/**
		 * Le fond (pour intercépter tous les events)
		 */
		protected var _back							:DisplayObject;
		
		/**
		 * Le delegate de cette liste
		 */
		protected var _delegate						:IVirtualListDelegate;
		
		/**
		 * La valeur du scroll
		 */
		protected var _currentScroll				:Number						= 0;
		
		/**
		 * La valeur du scroll callée au pixel
		 */
		protected var _roundedScroll				:int;
		
		/**
		 * L'index en début de liste
		 */
		protected var _firstIndex					:int;
		
		/**
		 * L'index en find de liste
		 */
		protected var _lastIndex					:int;
		
		/**
		 * Lorsque la liste a été déplacée (par l'utilisateur ou le code)
		 */
		protected var _onListMoved					:Signal						= new Signal();
		
		/**
		 * Lorsqu'un élément est ajouté
		 */
		protected var _onElementAdded				:Signal						= new Signal();
		
		/**
		 * Lorsqu'un élément est supprimé
		 */
		protected var _onElementDeleted				:Signal						= new Signal();
		
		/**
		 * La vélocité du mouvement de la liste
		 */
		protected var _velocity						:Number						= 0;
		
		/**
		 * L'inertie (1 infinie, 0 aucune)
		 */
		protected var _inertia						:Number						= .93;
		
		/**
		 * Le frein
		 */
		protected var _breakForce					:Number						= .15;
		
		/**
		 * Le frein de l'inertie quand la liste dépasse
		 */
		protected var _velocityOutBreak				:Number						= 1.5;
		
		/**
		 * La vélocité minimum pour appliquer le cran
		 */
		protected var _minimumStepVelocity			:Number						= 2;
		
		/**
		 * La propriété de positionnement pour cette orientation
		 */
		protected var _positionVar					:String						= "";
		
		/**
		 * Le propriété de taille pour cette orientation
		 */
		protected var _contentSizeVar				:String						= "";
		
		/**
		 * La propriété de delta sur les dragEvents pour cette orientation
		 */
		protected var _deltaTouchVar				:String						= "";
		
		/**
		 * La propriété pour le scale
		 */
		protected var _scaleVar						:String						= ""
		
		/**
		 * La direction a vérifier sur les dragEvents
		 */
		protected var _dragDirection				:String 					= "";
		
		/**
		 * Les variables de placement
		 */
		protected var _placementVar1				:String						= "";
		protected var _placementVar2				:String						= "";
		
		/**
		 * Les variables de placement du container
		 */
		protected var _containerPlacementVar1		:String						= "";
		protected var _containerPlacementVar2		:String						= "";
		
		/**
		 * L'orientation de la liste virtuelle
		 */
		protected var _orientation					:String						= "";
		
		/**
		 * Le surchargement des éléments autorisés (peut être le double en mémoire : avant le début et après la fin)
		 */
		protected var _elementsOverLoad				:int						= 0;
		
		/**
		 * Si on autorise le drag sur les dragEvents dont la direction est inconnue
		 */
		protected var _dragAllowUnknownDirection	:Boolean 					= true;
		
		/**
		 * Si le drag est vérouille
		 */
		protected var _dragLocked					:Boolean;
		
		/**
		 * Le premier index récupéré depuis le delegate
		 */
		protected var _firstElementIndex			:int;
		
		/**
		 * Le dernier index récupéré depuis le delegate
		 */
		protected var _lastElementIndex				:int;
		
		/**
		 * La taille typique de l'élément
		 */
		protected var _typicalElementSize			:int;
		
		/**
		 * Si on a besoin de vérifier les dépassements à la prochaine itération
		 */
		protected var _needBreak					:Boolean;
		
		/**
		 * Si on doit activer les pas sur le scroll
		 */
		protected var _step							:Boolean;
		
		/**
		 * Si on doit bloquer l'inertie entre chaque pas (forcer un cran par un cran)
		 */
		protected var _directStep					:Boolean;
		
		/**
		 * L'élément vers lequel aller lorsqu'on est en directStep
		 */
		protected var _destinationElementForStep	:ResizableComponent;
		
		/**
		 * Les éléments (courant / précédent / suivant) pour le directStep
		 */
		protected var _directStepPreviousElement	:ResizableComponent;
		protected var _directStepCenteredElement	:ResizableComponent;
		protected var _directStepNextElement		:ResizableComponent;
		
		
		
		/**
		 * Le type de masque appliqué
		 */
		public function get maskType ():uint { return _maskType; }
		public function set maskType (value:uint):void 
		{
			// Enregistrer le type de masque
			_maskType = value;
			
			// Gérer les masques
			if (_maskType == DOUBLE_MASK_TYPE)
			{
				_container.addChild(_firstMask);
				_container.addChild(_lastMask);
			}
			else if (_container.contains(_firstMask) && _container.contains(_lastMask))
			{
				_container.removeChild(_firstMask);
				_container.removeChild(_lastMask);
			}
		}
		
		/**
		 * Le delegate de cette liste
		 */
		public function get delegate ():IVirtualListDelegate { return _delegate; }
		public function set delegate (value:IVirtualListDelegate):void
		{
			// On a un delegate
			_delegate = value;
			
			// Actualiser
			listMovedHandler();
		}
		
		/**
		 * La position actuelle du scroll
		 */
		public function get currentScroll ():Number { return _currentScroll; }
		public function set currentScroll (value:Number):void 
		{
			// Enregistrer la nouvelle position
			_currentScroll = value;
			
			// Si on a un nouvel arrondi
			if (_roundedScroll != int(_currentScroll + .5))
			{
				// Enregistrer la nouvelle valeur de l'arrondi
				_roundedScroll = int(_currentScroll + .5)
				
				// La liste a bougé de plus ou egal 1px
				listMovedHandler();
			}
		}
		
		/**
		 * L'index de début de liste
		 */
		public function get firstIndex ():uint { return _firstIndex; }
		
		/**
		 * L'index de fin de liste
		 */
		public function get lastIndex ():uint { return _lastIndex; }
		
		/**
		 * Lorsque la liste a été déplacée (par l'utilisateur ou le code)
		 */
		public function get onListMoved ():Signal { return _onListMoved; }
		
		/**
		 * Lorsqu'un élément est ajouté
		 */
		public function get onElementAdded ():Signal { return _onElementAdded; }
		
		/**
		 * Lorsqu'un élément est supprimé
		 */
		public function get onElementDeleted ():Signal {return _onElementDeleted; }
		
		/**
		 * Si on autorise le drag sur les dragEvents dont la direction est inconnue
		 */
		public function get dragAllowUnknownDirection ():Boolean { return _dragAllowUnknownDirection; }
		public function set dragAllowUnknownDirection (value:Boolean):void 
		{
			_dragAllowUnknownDirection = value;
		}
		
		/**
		 * L'orientation de la liste
		 */
		public function get orientation ():String { return _orientation; }
		public function set orientation (value:String):void 
		{
			// Si on a des infos sur l'orientation
			if (value in ORIENTATIONS_VAR_NAMES)
			{
				// On enregistre l'orientation
				_orientation = value;
				
				// Parcourir les propriétés sur l'orientation et les définir sur cette liste
				for (var i:String in ORIENTATIONS_VAR_NAMES[_orientation])
				{
					this[i] = ORIENTATIONS_VAR_NAMES[_orientation][i];
				}
			}
		}
		
		/**
		 * Si on doit activer les pas sur le scroll
		 */
		public function get step ():Boolean { return _step; }
		public function set step (value:Boolean):void 
		{
			// Si c'est différent
			if (_step != value)
			{
				// Enregistrer la valeur
				_step = value;
				
				// Revérifier les dépassements
				_needBreak = true;
				
				// Adapter le scroll
				currentScroll += (_step ? 1 : -1) * _container[_contentSizeVar] / 2;
			}
		}
		
		/**
		 * La vélocité minimum pour appliquer le cran
		 */
		public function get minimumStepVelocity ():Number { return _minimumStepVelocity; }
		public function set minimumStepVelocity (value:Number):void 
		{
			_minimumStepVelocity = value;
		}
		
		/**
		 * Si on doit bloquer l'inertie entre chaque pas (forcer un cran par un cran)
		 */
		public function get directStep ():Boolean { return _directStep; }
		public function set directStep (value:Boolean):void 
		{
			// Enregistrer la valuer
			_directStep = value;
			
			// Annuler les propriétés de ciblage
			_directStepCenteredElement = null;
			_directStepPreviousElement = null;
			_directStepNextElement = null;
			_destinationElementForStep = null;
		}
		
		
		/**
		 * 
		 * @param	pDelegate
		 */
		public function VirtualList (pDelegate:IVirtualListDelegate, pOrientation:String = "vertical")
		{
			// Relayer la construction
			super();
			
			// Définir une orientation
			orientation = pOrientation;
			
			// Enregistrer le delegate
			delegate = pDelegate;
			
			// Initialiser les éléments
			//initBack();
			initMasks();
		}
		
		/**
		 * Initialiser le fond
		 */
		protected function initBack ():void
		{
			// Créer le fond
			_back = new Bitmap(__transparentBitmapData);
			
			// Ajouter le fond
			addChild(_back);
		}
		
		/**
		 * Initialiser les masques
		 */
		protected function initMasks ():void
		{
			// Créer les 2 masques
			_firstMask = new Bitmap(__opaqueBitmapData);
			_lastMask = new Bitmap(__opaqueBitmapData);
			
			// Rendre les masques opaques
			//_firstMask.opaqueBackground = false;
			//_lastMask.opaqueBackground = false;
			
			// Les passer bitmap
			_firstMask.cacheAsBitmap = true;
			_lastMask.cacheAsBitmap = true;
			
			// Les masquer
			_firstMask.visible = false;
			_lastMask.visible = false;
		}
		
		/**
		 * Initialisation du composant
		 */
		override protected function addedHandler (event:Event = null):void
		{
			// Relayer
			super.addedHandler(event);
			
			// Initialiser les intéractions
			initInteractions();
			
			// Activer la boucle
			addEventListener(Event.ENTER_FRAME, enterFrameHandler);
		}
		
		/**
		 * La boucle pour les déplacements du contenu
		 */
		protected function enterFrameHandler (event:Event):void 
		{
			// Le déplacement n'est pas vérouillé par un drag
			if (!_dragLocked)
			{
				// Appliquer la vélocité
				currentScroll += _velocity;
				
				// Limiter les dépassements de la liste
				if (_needBreak)
					breakVelocity();
				
				// Si on a assez de vélocité
				if (_velocity <= -.5 || _velocity >= .5)
				{
					// Appliquer l'inertie
					_velocity *= _inertia;
				}
				
				// On annule le peu d'inertie qu'on a
				else if (_velocity != 0)
				{
					_velocity = 0;
				}
			}
		}
		
		/**
		 * Initialiser les intéractions
		 */
		protected function initInteractions ():void 
		{
			// Ecouter les touch
			GestureTouchEmulator.emulate(this);
			
			// Ecouter la molette
			addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
		}
		
		/**
		 * Déplacement avec la molette
		 */
		protected function mouseWheelHandler (event:MouseEvent):void
		{
			if (_directStep)
			{
				_velocity -= _typicalElementSize / 2 * (event.delta > 0 ? 1 : -1);
			}
			else
			{
				_velocity -= event.delta * 5;
			}
		}
		
		/**
		 * Les évènements touch
		 * @param	event
		 */
		public function gestureTouchHandler (event:GestureTouchEvent):void 
		{
			// Si on est sur un event drag
			if (event is GestureTouchDragEvent)
			{
				// Récupérer l'event drag
				var dragEvent:GestureTouchDragEvent = event as GestureTouchDragEvent;
				
				// Début du drag en directStep
				if (_directStep && dragEvent.type == GestureTouchDragEvent.START_DRAG)
				{
					// Virer les anciennes références
					_directStepCenteredElement = null;
					_directStepPreviousElement = null;
					_directStepNextElement = null;
					_destinationElementForStep = null;
					
					// Parcourir les éléments
					const total:uint = _elements.length;
					for (var i:int = 0; i < total; i++)
					{
						// Cibler l'élément du centre
						if (
								_elements[i][_positionVar] >= _container[_contentSizeVar] / 2 - _typicalElementSize
								&&
								_elements[i][_positionVar] < _container[_contentSizeVar] / 2
							)
						{
							// Enregistrer l'élément du centre
							if (_elements[i].index >= _firstElementIndex && _elements[i - 1].index <= _lastElementIndex)
								_directStepCenteredElement = _elements[i];
							
							// Cibler l'élément précédent
							if ((i - 1) in _elements && _elements[i - 1].index >= _firstElementIndex)
								_directStepPreviousElement = _elements[i - 1];
							
							// Cibler l'élément suivant
							if ((i + 1) in _elements && _elements[i + 1].index <= _lastElementIndex)
								_directStepNextElement = _elements[i + 1];
						}
					}
					
					// Par défaut on cible l'élément du centre
					_destinationElementForStep = _directStepCenteredElement;
				}
				
				// Fin du drag en directStep
				else if (_directStep && dragEvent.type == GestureTouchDragEvent.STOP_DRAG)
				{
					// Si on a un élément au centre
					if (_directStepCenteredElement != null)
					{
						// Si on dépasse d'un cran vers l'élément d'après
						if (_directStepCenteredElement[_positionVar] + _typicalElementSize / 2 < _container[_contentSizeVar] / 2 - _typicalElementSize)
						{
							// On cible ce qu'on peut
							_destinationElementForStep = _directStepNextElement != null ? _directStepNextElement : _directStepCenteredElement;
						}
						
						// Si on dépase d'un cran vers l'élément d'avant
						else if (_directStepCenteredElement[_positionVar] + _typicalElementSize / 2 > _container[_contentSizeVar] / 2 + _typicalElementSize)
						{
							// On cible ce qu'on peut
							_destinationElementForStep = _directStepPreviousElement != null ? _directStepPreviousElement : _directStepCenteredElement;
						}
						
						// Si on va vers l'élément suivant
						else if (_directStepCenteredElement[_positionVar] + _typicalElementSize / 2 < _container[_contentSizeVar] / 2)
						{
							// Et que la vélocité est significativement négative
							if (_velocity < -1 && _directStepNextElement != null)
							{
								// On va vers l'élément suivant
								_destinationElementForStep = _directStepNextElement;
							}
						}
						
						// Sinon si on va vers l'élément précédent
						else if (_directStepCenteredElement[_positionVar] + _typicalElementSize / 2 > _container[_contentSizeVar] / 2)
						{
							// Et que la vélocité est significativement positive
							if (_velocity > 1 && _directStepPreviousElement != null)
							{
								// On va vers l'élément précédent
								_destinationElementForStep = _directStepPreviousElement;
							}
						}
						
						// Sinon
						else
						{
							// On retourne au centre
							_destinationElementForStep = null;
						}
					}
				}
				
				// Un point vient dérouiller le déplacement (on a pas encore de direction)
				if (event.type == GestureTouchDragEvent.LOCK_DRAG)
				{
					// Pas de vélocité
					_velocity = 0;
					
					// Le drag est vérouille
					_dragLocked = true;
				}
				
				// C'est dévérouillé
				else if (event.type == GestureTouchDragEvent.UNLOCK_DRAG || event.type == GestureTouchDragEvent.STOP_DRAG)
				{
					// Le drag est dévérouillé
					_dragLocked = false;
					
					// Actualiser
					listMovedHandler();
				}
				
				// Vérifier si on est dans la bonne direction pour le drag
				else if ((dragEvent[_dragDirection] || (dragEvent.isBoth && _dragAllowUnknownDirection)) && event.type == GestureTouchDragEvent.DRAGGING)
				{
					// Récupérer la vélocité du point
					_velocity = dragEvent[_deltaTouchVar];
					
					// Lui appliquer une limite en cas de dépassement
					breakVelocity();
					
					// On applique cette vélocité au scroll
					currentScroll += _velocity;
				}
			}
		}
		
		/**
		 * Besoin d'un rafraichissement (revérifie les index des éléments)
		 */
		public function update ():void
		{
			// Actualiser
			listMovedHandler();
		}
		
		/**
		 * Rafraichir la liste (effacer tous les éléments et reconstruire)
		 */
		public function reset ():void
		{
			delegate = delegate;
			
			/*
			var oldDelegate:IVirtualListDelegate = _delegate;
			if (oldDelegate != null)
			{
				delegate = null;
				delegate = oldDelegate;
			}
			*/
		}
		
		/**
		 * Afficher un index spécifique
		 * @param	pIndex : L'index en question
		 * @param	pAnimationDuration : La durée de l'animation
		 * @param	pAnimationEase : Le easing de l'animation
		 */
		public function showIndex (pIndex:int = 0, pAnimationDuration:Number = 0, pAnimationEase:Function = null):void
		{
			
		}
		
		/**
		 * Freiner la liste
		 */
		protected function breakVelocity (pImmediate:Boolean = false):void
		{
			// La limite de la liste (NaN si pas de limite dépassée)
			var limit:Number;
			
			// La position du premier élément
			var firstElementPosition:Number;
			
			// La position du dernier élément (sa position + sa taille !)
			var lastElementPosition:Number;
			
			// L'élément qui est au centre
			var centeredElement:ResizableComponent;
			
			// On vérifie si on dépasse sur une limite (début ou fin, début prioritaire)
			var i:uint;
			var total:uint = _elements.length;
			for (i = 0; i < total; i++)
			{
				// Cibler l'élément du centre
				if (
						_elements[i][_positionVar] >= _container[_contentSizeVar] / 2 - _typicalElementSize
						&&
						_elements[i][_positionVar] < _container[_contentSizeVar] / 2
					)
				{
					// Enregistrer l'élément du centre
					centeredElement = _elements[i];
				}
				
				// Vérifier si on est sur le premier élément
				if (_elements[i].index == _firstElementIndex)
				{
					// Enregistrer la position du premier élément
					firstElementPosition = _elements[i][_positionVar];
					
					// Si on a des pas
					if (_step)
					{
						// Si le premier élément dépasse de la moitié du container
						if (firstElementPosition > _container[_contentSizeVar] / 2 - _typicalElementSize / 2)
						{
							// La limite est à la moitié
							limit = firstElementPosition - _container[_contentSizeVar] / 2 + _typicalElementSize / 2;
						}
					}
					
					// On est en scroll libre donc les limites sont au bord
					// Si le premier élément dépasse du container
					else if (firstElementPosition > 0)
					{
						// On a donc une limite
						limit = firstElementPosition;
					}
				}
				
				// Si on est sur le dernier élément
				if (_elements[i].index == _lastElementIndex)
				{
					// On récupère sa position (et sa taille !)
					lastElementPosition = _elements[i][_positionVar] + _elements[i][_contentSizeVar];
					
					// Si sa position dépasse du container
					if (_step)
					{
						// Si on dépasse de la moitié
						if (lastElementPosition < _container[_contentSizeVar] / 2 + _typicalElementSize / 2)
						{
							// La limite est à la moitié
							limit = lastElementPosition - _container[_contentSizeVar] / 2 - _typicalElementSize / 2;
						}
					}
					
					// On est en scroll libre donc les limites sont au bord
					else if (lastElementPosition < _container[_contentSizeVar])
					{
						// On a une limite
						limit = lastElementPosition - _container[_contentSizeVar] - 1;
					}
				}
			}
			
			// Si le premier et le dernier éléments sont dans la vue
			if (
					!_step
					&&
					(firstElementPosition >= 0 || firstElementPosition < 0)
					&&
					(lastElementPosition >= 0 || lastElementPosition < 0)
					&&
					lastElementPosition - firstElementPosition < _container[_contentSizeVar]
				)
			{
				// La limite est sur le premier élément
				limit = firstElementPosition;
			}
			
			// Si on a un step
			if (_step && !_dragLocked && isNaN(limit))
			{
				// Si on doit faire pas à pas
				if (_directStep && _destinationElementForStep != null)
				{
					// Aller vers l'élément de destination
					limit = _destinationElementForStep[_positionVar] - _container[_contentSizeVar] / 2 + _typicalElementSize / 2;
					
					// Annuler la vélocité
					_velocity = 0;
				}
				
				// Si on est en mode pas, qu'on a pas de limite définie
				// Et si la vélocité commence a se faire faible
				else if (centeredElement != null && Math.abs(_velocity) < _minimumStepVelocity)
				{
					// Récupérer l'élément qui est au milieu
					limit = centeredElement[_positionVar] - _container[_contentSizeVar] / 2 + _typicalElementSize / 2;
					
					// On annule la vélocité
					_velocity = 0;
				}
			}
			
			// Si on a une limite
			if (limit < 0 || limit >= 0)
			{
				// Si le drag n'est pas vérouillé
				if (!_dragLocked)
				{
					// On freine le scroll vers cette limite
					currentScroll -= (currentScroll - (currentScroll - limit)) * (pImmediate ? 1 : _breakForce);
				}
				
				// Diviser la vélocité
				_velocity = _velocity / _velocityOutBreak;
				
				// Si on est proche de la destination
				if (Math.abs(currentScroll - (_currentScroll - limit)) < .1 && _velocity > -.5 && _velocity < .5)
				{
					// On ne vérifie plus si on dépasse
					_needBreak = false;
					
					// On place à la destination
					currentScroll = currentScroll - limit;
					
					// Annuler les ciblages du directStep
					_directStepCenteredElement = null;
					_directStepPreviousElement = null;
					_directStepNextElement = null;
					_destinationElementForStep = null;
				}
			}
		}
		
		/**
		 * Construire les éléments
		 */
		protected function buildElements ():void
		{
			// Si on a un delegate
			if (_delegate != null && _localHeight > 0 && _localWidth > 0)
			{
				// Les éléments ajoutés
				var addedElement:ResizableComponent;
				var currentIndex:int;
				
				// Si on a pas d'élément, on demande le courant
				if (_elements.length == 0)
				{
					// Ajouter le premier
					needElementAt(int((- _currentScroll) / _typicalElementSize + .5), 0);
				}
				
				// Si on a au moins un élément
				if (_elements.length > 0)
				{
					// On ajoute les éléments dont on a besoin au début
					while (_elements[0][_positionVar] > - _container[_containerPlacementVar1])
					{
						// Ajouter à la suite au début
						addedElement = needElementAt(_elements[0].index - 1, 0);
						
						// Si ce dernier est disponible
						if (addedElement != null)
						{
							// Placer cet élément sous l'élément de l'index suivant
							addedElement[_positionVar] = _elements[1][_positionVar] - addedElement[_contentSizeVar];
						}
						else
						{
							// Arrêter l'ajout
							break;
						}
					}
					
					// On ajoute les éléments dont on a besoin à la fin
					while (getElementAt(-1)[_positionVar] + getElementAt(-1)[_contentSizeVar] < _container[_contentSizeVar] + _container[_containerPlacementVar2])
					{
						// Ajouter à la suite à la fin
						addedElement = needElementAt(getElementAt(-1).index + 1, -1);
						
						// Si ce dernier est disponible
						if (addedElement != null)
						{
							// Placer cet élément sous l'élément de l'index suivant
							addedElement[_positionVar] = getElementAt(-2)[_positionVar] + getElementAt(-2)[_contentSizeVar];
						}
						else
						{
							// Arrêter l'ajout
							break;
						}
					}
				}
			}
			
			// Si on a une taille
			if (_localHeight > 0 && _localWidth > 0)
			{
				// Parcourir tous les éléments de la liste
				var total:uint = _elements.length;
				for (var i:uint = 0; i < total; i++)
				{
					// Si on a pas de delegate
					if (_delegate == null)
					{
						// Cibler l'élément à supprimer
						var deletedElement:ResizableComponent = _elements[i];
						
						// On supprime cet élément
						removeElement(deletedElement);
						total --;
						i--;
						
						// Signaler
						_onElementDeleted.dispatch(deletedElement);
					}
					
					// Si notre élément dépasse même après l'overload
					if (
						// Si on est pas en déplacement
						!_dragLocked
						&&
						(
							// Si on dépasse du cadre exterieur avant le début
							_elements[i][_positionVar] + _elements[i][_contentSizeVar] <= - _container[_containerPlacementVar1] - _elementsOverLoad * _typicalElementSize
							||
							// Ou si on dépasse après la fin
							_elements[i][_positionVar] >= _container[_contentSizeVar] + _container[_containerPlacementVar2] + _elementsOverLoad * _typicalElementSize
						)
					   )
					{
						// On va supprimer
						if (
								// On ne supprime pas le premier ni le dernier élément indexé
								(_elements[i].index != _firstElementIndex || _elements[i][_positionVar] <= 0)
								&&
								(_elements[i].index != _lastElementIndex || _elements[i][_positionVar] >= _container[_contentSizeVar])
							)
						{
							// On supprime cet élément
							removeElement(_elements[i]);
							total --;
							i--;
						}
						
						// Sinon on masque juste
						else
						{
							_elements[i].visible = false;
						}
					}
					
					// Sinon si on dépasse de la liste mais pas de l'overload
					else if (
								_elements[i][_positionVar] + _elements[i][_contentSizeVar] <= - _container[_containerPlacementVar1]
								||
								_elements[i][_positionVar] >= _container[_contentSizeVar] + _container[_containerPlacementVar2]
							)
					{
						// Masquer cet élément
						_elements[i].visible = false;
					}
					
					// Sinon on est dans la liste
					else if (!_elements[i].visible)
					{
						// Donc afficher cet élément
						_elements[i].visible = true;
					}
				}
			}
		}
		
		/**
		 * La liste a bougé
		 */
		protected function listMovedHandler ():void
		{
			// Récupérer les valeurs du delegate
			getDelegateValues();
			
			// Placer les éléments selon cette nouvelle position
			placeElements();
			
			// Construire les nouveaux éléments dont on a besoin pour remplir la liste
			buildElements();
			
			// Actualiser les masques
			updateMasks();
			
			// Vu qu'on a bougé, vérifier les dépassements à la prochaine itération
			_needBreak = true;
			
			// Signaler qu'on a bougé
			_onListMoved.dispatch();
		}
		
		/**
		 * Récupérer les valeurs du delegate pour le prochain traitement
		 */
		protected function getDelegateValues ():void 
		{
			// Si on a un delegate
			if (_delegate != null)
			{
				// On récupérer les index et la taille moyenne
				_firstElementIndex = _delegate.getVListFirstElementIndex(this);
				_lastElementIndex = _delegate.getVListLastElementIndex(this);
				_typicalElementSize = _delegate.getVListTipicalElementSize(this);
			}
			else
			{
				_firstElementIndex = 0;
				_lastElementIndex = 0;
				_typicalElementSize = 0;
			}
		}
		
		/**
		 * Placer les éléments
		 */
		protected function placeElements ():void
		{
			// L'élément précédent
			var lastElement:ResizableComponent;
			
			// Parcourir les éléments
			const total:uint = _elements.length;
			for (var i:uint = 0; i < total; i++)
			{
				// Placer selon la taille estimée
				if (i == 0)
				{
					// Placer le premier élément
					_elements[i][_positionVar] = _elements[i].index * _typicalElementSize + _roundedScroll;
				}
				else
				{
					// Sinon on place par rapport à l'ancien élément
					_elements[i][_positionVar] = int(lastElement[_positionVar] + lastElement[_contentSizeVar] + .5);
				}
				
				// Cibler l'ancien élément
				lastElement = _elements[i];
			}
		}
		
		/**
		 * Actualiser les masques
		 */
		protected function updateMasks ():void
		{
			// Si on a un masque
			if (_maskType != NO_MASK_TYPE)
			{
				// Les variables pour la position
				var isFirst:Boolean;
				var isLast:Boolean;
				var isBeforeFirst:Boolean = true;
				var isAfterLast:Boolean = false;
				
				// Parcourir les éléments
				const total:uint = _elements.length;
				for (var i:uint = 0; i < total; i++)
				{
					// Si c'est le premier ou le dernier (visible de la liste)
					isFirst = _elements[i][_positionVar] <= 0 && _elements[i][_positionVar] >= - _elements[i][_contentSizeVar];
					isLast = _elements[i][_positionVar] >= _container[_contentSizeVar] - _elements[i][_contentSizeVar] && _elements[i][_positionVar] <= _container[_contentSizeVar];
					
					// Si c'est le premier, alors maintenant on est plus avant le premier
					if (isFirst)
						isBeforeFirst = false;
					
					// Actualiser le masque de cet élément
					updateMaskForElement(_elements[i], i, isBeforeFirst, isFirst, isLast, isAfterLast);
					
					// Si c'est le dernier alors maintenant on est après le dernier
					if (isLast)
						isAfterLast = true;
				}
			}
		}
		
		/**
		 * Actualiser le masque
		 */
		protected function updateMaskForElement (pElement:ResizableComponent, pIndex:int, pBeforeFirst:Boolean, pFirst:Boolean, pLast:Boolean, pAfterLast:Boolean):void
		{
			// Gérer la visibilité des éléments qui dépassent
			if (pBeforeFirst || pAfterLast)
				pElement.visible = false;
			else
				pElement.visible = true;
			
			// Double masque
			if (_maskType == DOUBLE_MASK_TYPE)
			{
				if (pFirst)
					pElement.mask = _firstMask;
				else if (pLast)
					pElement.mask = _lastMask;
				else
					pElement.mask = null;
			}
			
			// Masque Alpha ou Scale
			else if (_maskType == ALPHA_MASK_TYPE || _maskType == SCALE_MASK_TYPE)
			{
				// Le nom de la 
				const varName:String = (_maskType == ALPHA_MASK_TYPE ? "alpha" : _scaleVar)
				
				// Si on est sur le premier
				if (pFirst)
				{
					// On applique la valeur entre 0 et 1
					pElement[varName] = Math.max(0, Math.min((pElement[_contentSizeVar] + pElement[_positionVar]) / pElement[_contentSizeVar], 1));
					
					// Bloquer la position en haut
					if (_maskType == SCALE_MASK_TYPE && pElement[varName] < 1)
						pElement[_positionVar] = 0;
				}
				
				// Si c'est le dernier, on appliquer juste la valeur entre 0 et 1
				else if (pLast)
					pElement[varName] = Math.max(0, Math.min((_container[_contentSizeVar] - pElement[_positionVar]) / pElement[_contentSizeVar], 1));
				
				// Sinon, on affiche comme les autres
				else
					pElement[varName] = 1;
			}
		}
		
		/**
		 * Besoin d'un élément à un index data vers un index d'élément
		 * @param	pFromIndex : L'index data par rapport aux données
		 * @param	pToIndex : L'index de l'élément dans les éléments (-1 pour ajouter à la fin)
		 */
		protected function needElementAt (pFromIndex:int, pToIndex:int = -1):ResizableComponent
		{
			// Si on a un delegate
			if (_delegate != null)
			{
				// Demander cet élément au delegate
				var element:ResizableComponent = _delegate.getVListElementAt(this, pFromIndex);
				
				// Si on a un élément
				if (element != null)
				{
					// Lui spécifier son index
					element.index = pFromIndex;
					
					// Si le composant n'a pas de taille de base, on pioche l'info dans le delegate
					if (element[_contentSizeVar] == 0)
						element[_contentSizeVar] = _typicalElementSize;
					
					// Spécifier les dimensions du container
					element[_placementVar1] = 0;
					element[_placementVar2] = 0;
					
					// On l'ajoute
					addElement(element, pToIndex);
					
					// Ecouter ses redimensionnements
					// Bien écouter après l'addElement sinon ça bug
					element.onResized.add(listMovedHandler);
					
					// Signaler
					_onElementAdded.dispatch(element);
				}
				
				// Retourner cet élément
				return element;
			}
			else
			{
				return null;
			}
		}
		
		/**
		 * Un élément est supprimé
		 */
		override protected function internalRemoveElement (pElement:ResizableComponent):void
		{
			// Supprimer l'écoute du changement de taille de l'élément
			pElement.onResized.remove(listMovedHandler);
			
			// Relayer
			super.internalRemoveElement(pElement);
		}
		
		/**
		 * Redimensionnement du composant
		 */
		override protected function needReplace (pNeedResized:Boolean = false):void
		{
			// Relayer
			super.needReplace(pNeedResized);
			
			// Replacer le fond
			if (_back != null)
			{
				_back.width = _localWidth;
				_back.height = _localHeight;
			}
		}
		
		/**
		 * Le contenu a été redimensionné
		 */
		override protected function containerResized ():void
		{
			// Redimensionner les masques au niveau du container
			_firstMask.width = _lastMask.width = _container.width;
			_firstMask.height = _lastMask.height = _container.height;
			
			// Rafraichir
			listMovedHandler();
			
			// Replacer
			breakVelocity(true);
		}
		
		/**
		 * Déstruction
		 */
		override public function dispose ():void
		{
			// Ne plus émuler cet éléments
			GestureTouchEmulator.demulate(this);
			
			// Désactiver les listeners
			removeEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
			removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
			
			// Relayer
			super.dispose();
		}
	}
}